#include<stdio.h>
#include<stdlib.h>
#include "analyzer.h"

/**
 * 
 * @param add_node
 * @return
 */
int analyzer_check_add_type(AST* add_node)
{
    switch (add_node->type) {
        case AST_ADD: analyzer_check_add_type(add_node->sons[0]);
        analyzer_check_add_type(add_node->sons[0]);
    }
}

/**
 * Gets the token data type (int or byte)

 * <p>
 * <b>Attention:</b> It will work just if the check for declared variables was done before.
 * </p>
 * 
 * @param token
 * @return the token type (int or byte)
 */
int analyzer_get_token_type(AST* token)
{
    int type = SYMBOL_UNDEFINED;
    switch(token->type) {
        case AST_IDENTIFIER:
        case AST_VAR_DEF:
        case AST_VEC_DEF:
        case AST_FUNC_DEF:
            // get the identifier type that is the value of the assign operation
            // and checks if it is the same type of the left side identifier
            type = token->symbol[0]->data->dataType;
            break;

        case AST_LIT_INT:
            type = SYMBOL_INT_TYPE;
            break;

        case AST_LIT_CHAR:
            type = SYMBOL_BYTE_TYPE;
            break;

        case AST_FUNC_CALL:
            type = token->symbol[0]->data->dataType;
            break;

        case AST_PARAM:
            type = token->symbol[0]->data->dataType;
            break;
    }

    return type;
}


/**
 * Checks if there is any not declared identifier in the code
 * @param hash_table the hash table reference
 * @return 1 if there is any not declared identifier, 0 otherwise
 */
int analyzer_check_for_not_declared_identifier(HASH_TABLE* hash_table)
{
    int i;
    int isThereNotDeclaredIdentifiers = 0;

    for (i = 0; i < TABLE_SIZE; i++) {
        HASH_NODE* current_node = hash_table->entries[i];
        while(current_node != NULL) {
            if (current_node->data->type == SYMBOL_IDENTIFIER) {
                isThereNotDeclaredIdentifiers = 1;
                printf("[ERROR] Identifier %s was not declared. Line: %d.\n", current_node->data->lexeme, current_node->data->line_num);
            }
            current_node = current_node->next;
        }
    }

    return isThereNotDeclaredIdentifiers;
}

/**
 * Change the hash node type field to the correct identifier type.
 *
 * <p>
 * The first time that a identifier is processed, it's type is SYMBOL_IDENTIFIER,
 * whitch means that it was not processed yet. After the processes, if there is any identifier
 * with type SYMBOL_IDENTIFIER, it means that this identifier was not declared.
 * </p>
 *
 * @param ast_node the AST node
 * @param type the correct type of the identifier
 * @return 1 if there is at least one not declared identifier, 0 otherwise
 */
int analyzer_process_declaration(AST* ast_node, int type)
{
    if (ast_node->symbol[0]->data->type == SYMBOL_IDENTIFIER) {
        ast_node->symbol[0]->data->type = type;
        return 0;
    }

    printf("[ERROR] Identifier %s was already declared. Line: %d.\n", ast_node->symbol[0]->data->lexeme, ast_node->line_number);
    return 1;
}

/**
 * Converts the AST identifier type to the hash table identifier type.
 * @param ast_type AST identifier type to be converted
 * @return the hash table identifier type
 */
int analyzer_get_identifier_type(int ast_type)
{
    switch (ast_type) {
        case AST_INT_TYPE: return SYMBOL_INT_TYPE;
        case AST_BYTE_TYPE: return SYMBOL_BYTE_TYPE;
        default: return SYMBOL_UNDEFINED;
    }
}

/**
 * Walk the Abstract Syntaxe Tree changing identifiers types
 * to the correct type according to the grammar rules (var declaration, vector declaration, etc...)
 *
 * @param ast_node a node of the AST
 * @param ast_type the AST node type
 * @return 0 if there is no duplicate declarations, 1 otherwise
 */
int analyzer_set_declaration_type(AST* ast_node)
{
    int identifierAlreadyExists = 0;

    switch(ast_node->type)
    {
        case AST_VAR_DEF:
            identifierAlreadyExists = analyzer_process_declaration(ast_node, SYMBOL_VAR_DEC);
            ast_node->symbol[0]->data->dataType = analyzer_get_identifier_type(ast_node->sons[0]->type);
            break;
        case AST_VEC_DEF:
            identifierAlreadyExists = analyzer_process_declaration(ast_node, SYMBOL_VEC_DEC);
            ast_node->symbol[0]->data->dataType = analyzer_get_identifier_type(ast_node->sons[0]->type);
            break;
        case AST_FUNC_DEF:
            identifierAlreadyExists = analyzer_process_declaration(ast_node, SYMBOL_FUNC_DEC);
            ast_node->symbol[0]->data->dataType = analyzer_get_identifier_type(ast_node->sons[0]->type);

            // if function has parameters list
            if (ast_node->sons[1]) {
                analyzer_set_declaration_type(ast_node->sons[1]);
            }
            break;
        case AST_FUNC_PARAM_LIST:
            analyzer_set_declaration_type(ast_node->sons[0]);

            if (ast_node->sons[1]) {
                analyzer_set_declaration_type(ast_node->sons[1]);
            }
            break;
        case AST_PARAM:
            identifierAlreadyExists = analyzer_process_declaration(ast_node, SYMBOL_FUNC_DEC_PARAM);
            ast_node->symbol[0]->data->dataType = analyzer_get_identifier_type(ast_node->sons[0]->type);
            break;
        case AST_PARAM_LIST:
            analyzer_set_declaration_type(ast_node->sons[0]);

            if (ast_node->sons[1]) {
                analyzer_set_declaration_type(ast_node->sons[1]);
            }
            break;
    }

    return identifierAlreadyExists;
}

/**
 * Checks if an identifier was declared more than one time.
 * @param declarations_list_node an AST list of declarations
 * @return 1 if there is any duplicated declaration, 0 otherwise
 */
int analyzer_check_for_duplicated_identifiers(AST* declarations_list_node)
{
    int isThereDuplication = 0;
    if (declarations_list_node->type == AST_DEFS) {
        int result = analyzer_set_declaration_type(declarations_list_node->sons[0]);
        if (result) {
            isThereDuplication = result;
        }
        
        if (declarations_list_node->sons[1]) {
            int result = analyzer_check_for_duplicated_identifiers(declarations_list_node->sons[1]);
            if (result) {
                isThereDuplication = result;
            }
        }
    }

    return isThereDuplication;
}

/**
 * Checks the use of an identifier in an assign operation.
 *
 * @param assign_node AST node representing an assign operation
 * @return 1 if the use is not correct, 0 otherwise
 */
int analyzer_check_assign_use(AST* assign_node)
{
    if (assign_node->symbol[0]->data->type != SYMBOL_VAR_DEC &&
        assign_node->symbol[0]->data->type != SYMBOL_IDENTIFIER) {
        printf("[ERROR] Identifier %s is been used as a variable but it was not declared as one. Line: %d.\n", assign_node->symbol[0]->data->lexeme, assign_node->line_number);
        return 1;
    }

    if (analyzer_check_types_assign(assign_node->symbol[0]->data->dataType, assign_node->sons[0])) {
        printf("[ERROR] The assign operation does not respect the identifier %s declared type on line: %d.\n", assign_node->symbol[0]->data->lexeme, assign_node->line_number);
        return 1;
    }

    return 0;
}

/**
 * Checks the use of an identifier in a vector assign operation.
 *
 * @param assign_node AST node representing a vector assign operation
 * @return 1 if the use is not correct, 0 otherwise
 */
int analyzer_check_vec_assign_use(AST* vec_assign_node)
{
    if (vec_assign_node->symbol[0]->data->type != SYMBOL_VEC_DEC &&
        vec_assign_node->symbol[0]->data->type != SYMBOL_IDENTIFIER) {
        printf("[ERROR] Identifier %s is been used as a vector variable but it was not declared as one. Line: %d.\n", vec_assign_node->symbol[0]->data->lexeme, vec_assign_node->line_number);
        return 1;
    }

   if (analyzer_check_types_assign(vec_assign_node->symbol[0]->data->dataType, vec_assign_node->sons[1])) {
        printf("[ERROR] The assign operation does not respect the identifier %s declared type on line: %d.\n", vec_assign_node->symbol[0]->data->lexeme, vec_assign_node->line_number);
        return 1;
    }

    AST* vec_index_expr = vec_assign_node->sons[0];
    if (analyzer_get_token_type(vec_index_expr) != SYMBOL_INT_TYPE) {
        printf("[ERROR] The vector index must be an integer expression. Line: %d.\n", vec_assign_node->line_number);
        return 1;
    }

    return 0;
}

/**
 * Checks the use of an identifier in a function call.
 *
 * @param assign_node AST node representing a function call
 * @return 1 if the use is not correct, 0 otherwise
 */
int analyzer_check_func_call(AST* func_call_node)
{
    if (func_call_node->symbol[0]->data->type != SYMBOL_FUNC_DEC &&
        func_call_node->symbol[0]->data->type != SYMBOL_IDENTIFIER) {
        printf("[ERROR] Identifier %s is been used as a function call but it was not declared as one. Line: %d.\n", func_call_node->symbol[0]->data->lexeme, func_call_node->line_number);
        return 1;
    }

    return analyzer_check_func_call_params(func_call_node);
}

/**
 * Check if the list of params of a function call respects the declared list of params
 * @param func_call_node
 * @return 1 if there is any bad param, 0 otherwise
 */
int analyzer_check_func_call_params(AST* func_call_node)
{
    // TODO
    // 1. check if func call params respects the func call declaration
    // 2. if there is any bad param, a message should be printed out to the user
    //      informing wich param is wrong and return 1;

    // func declaration parameters list
    AST* func_def_params_node = func_call_node->symbol[0]->data->ast_declaration_node->sons[1];
    AST* func_call_params_node = func_call_node->sons[0];

    if (!func_def_params_node && !func_call_params_node) {
        return 0;
    }

    if (func_def_params_node && !func_call_params_node) {
        printf("[ERROR] You can not call function %s with no args. Line %d\n", func_call_node->symbol[0]->data->lexeme, func_call_node->line_number);
        return 1;

    } else if (!func_def_params_node && func_call_params_node) {
        printf("[ERROR] You can not call function %s with args. Line %d\n", func_call_node->symbol[0]->data->lexeme, func_call_node->line_number);
        return 1;
    }

    return analyzer_check_func_call_params_recursivly(func_call_node, func_def_params_node, func_call_params_node);
}

int analyzer_check_func_call_params_recursivly(AST* func_call_node, AST* func_def_params_node, AST* func_call_params_node)
{
    int check = 0;
    
    if (func_call_params_node && func_def_params_node) {

        AST* param = func_def_params_node->sons[0];
        AST* arg = func_call_params_node->sons[0];

        int check = analyzer_check_func_call_params_recursivly(func_call_node, func_def_params_node->sons[1], func_call_params_node->sons[1]);

        if (analyzer_get_token_type(param) != analyzer_get_token_type(arg)) {

            printf("[ERROR] Function call does not respect function declared parameters on line: %d\n", func_call_node->line_number);
            check = 1;
        }
    } else if (!func_call_params_node && func_def_params_node ||
               func_call_params_node && !func_def_params_node) {
        printf("[ERRORa asd asd ] Function call does not respect function declared parameters on line: %d\n", func_call_node->line_number);
        check = 1;
    }

    return check;
}


/**
 * Check the use of an identifier in all possible identifiers operations (var assign, vector assign or function call).
 * @param node An AST node
 * @return 1 if there is any bad use of the identifier, 0 otherwise
 */
int analyzer_check_identifier_use(AST* node)
{
    int isThereBadUse = 0;
    
    if (node) {

        switch(node->type) {
            case AST_ASS:
                isThereBadUse = analyzer_check_assign_use(node);
                break;
            case AST_ASS_VEC:
                isThereBadUse = analyzer_check_vec_assign_use(node);
                break;
            case AST_FUNC_CALL:
                isThereBadUse = analyzer_check_func_call(node);
                break;
        }

        if (analyzer_check_identifier_use(node->sons[0])) {
            isThereBadUse = 1;
        }
        
        if (analyzer_check_identifier_use(node->sons[1])) {
            isThereBadUse = 1;
        }

        if (analyzer_check_identifier_use(node->sons[2])) {
            isThereBadUse = 1;
        }

        if (analyzer_check_identifier_use(node->sons[3])) {
            isThereBadUse = 1;
        }
    }

    return isThereBadUse;
}

/**
 * Compares an <code>expr</code> type to a <code>target_type</code>
 *
 * @param target_type
 * @param expr
 * @return 1 if the <code>expr</code> type is equals to the <code>target_type</code>
 */
int analyzer_compare_expr_type_to_target(int target_type, AST* expr)
{
    int value_type;
    switch(expr->type) {
        case AST_IDENTIFIER:
            // get the identifier type that is the value of the assign operation
            // and checks if it is the same type of the left side identifier
            value_type = expr->symbol[0]->data->dataType;
            break;

        case AST_LIT_INT:
            value_type = SYMBOL_INT_TYPE;
            break;

        case AST_LIT_CHAR:
            value_type = SYMBOL_BYTE_TYPE;
            break;

        case AST_FUNC_CALL:
            value_type = expr->symbol[0]->data->dataType;
            break;

        case AST_ADD:
        case AST_SUB:
        case AST_MUL:
        case AST_DIV:
        case AST_OPERATOR_AND:
        case AST_OPERATOR_EQ:
        case AST_OPERATOR_G:
        case AST_OPERATOR_GE:
        case AST_OPERATOR_L:
        case AST_OPERATOR_LE:
        case AST_OPERATOR_NE:
        case AST_OPERATOR_NOT:
        case AST_OPERATOR_OR:
        case AST_EXPR_PAR:
            return analyzer_compare_expr_type_to_target(target_type, expr->sons[0]) &&
                   analyzer_compare_expr_type_to_target(target_type, expr->sons[0]);

        default: value_type = !target_type;
    }

    return target_type == value_type;
}

//int analyzer

/**
 * Checks if there is any assign error type between the targer identifier and it's value
 * @param assign_operation the assign operation
 * @return 1 if there is any assign type error, 0 otherwise
 */
int analyzer_check_types_assign(int identifier_type, AST* expr)
{
    int isEqual = analyzer_compare_expr_type_to_target(identifier_type, expr);

    if (!isEqual) {
        return 1;
    }
    
    return 0;
}